<?php

/**
 * 检测和规范输入数据接口
 *
 * 类型参数说明：
 * 	'noclean'  - 不做处理
 * 	'int'      - 转换成integer
 * 	'unit'     - 转换成无符号integer
 *  'num'      - 转换成number
 * 	'str'      - 转换成string，并去除两边的空格
 * 	'notrim'   - 转换成string，保留空格
 * 	'arr'      - 转换成array
 * 	'file'     - 转换成file，不支持数组提交
 * 	'html'     - HTML提交，不支持数组提交
 * 	'richhtml' - HTML提交，支持更多标签，不支持数组提交
 * 
 * @author yuanzhen
 * @todo HTML,RICHHTML
 * @todo 字符编码错误处理
 */
class YTool_Input
{

    private static $globalSources = array(
        'g' => '_GET',
        'p' => '_POST',
        'c' => '_COOKIE',
        'r' => '_REQUEST',
        'f' => '_FILES'
    );

    /**
     * 获取输入
     */
    public static function clean($source, $varname, $type = 'noclean')
    {
        assert(self::$globalSources[$source]);

        self::processMagicQuotes();

        $container = $GLOBALS[self::$globalSources[$source]];
        $var = isset($container[$varname]) ? $container[$varname] : '';

        return self::cast($var, $type);
    }

    private static function processMagicQuotes()
    {
        static $hasProcessed = false;

        if (!$hasProcessed && get_magic_quotes_gpc())
        {
            self::stripslashesDeep($_GET);
            self::stripslashesDeep($_POST);
            self::stripslashesDeep($_COOKIE);
            self::stripslashesDeep($_REQUEST);

            $hasProcessed = true;
        }
    }

    private static function &cast($data, $type)
    {
        switch ($type)
        {
            case 'noclean':
                break;
            case 'int':
                $data = intval($data);
                break;
            case 'uint':
                $data = max(0, intval($data));
                break;
            case 'num':
                $data = $data + 0;
                break;
            case 'str':
                $data = trim(self::getStr($data));
                break;
            case 'notrim':
                $data = self::getStr($data);
                break;
            case 'file':
                if (!is_array($data))
                {
                    $data = array(
                        'name' => '',
                        'type' => '',
                        'size' => 0,
                        'tmp_name' => '',
                        'error' => UPLOAD_ERR_NO_FILE,
                    );
                }
                break;
            case 'arr':
                break;
            case 'json':
                $data = trim(YStr_Check::sanitizeUTF8(strval($data)));
                if ($data)
                {
                    $data = json_decode($data, true);
                    self::toGbkDeep($data);
                }
                else
                {
                    $data = array();
                }
                break;
            default:
                assert(false);
        }
        return $data;
    }

    private static function getStr($data)
    {
        $str = YStr_Check::sanitizeUTF8(strval($data));
        return YStr_Encoding::togbk($str);
    }

    /**
     * 递归 stripslashes
     */
    private static function stripslashesDeep(&$value)
    {
        if (is_array($value))
        {
            foreach ($value as $sKey => $vVal)
            {
                self::stripslashesDeep($vVal);
            }
        }
        else if (is_string($value))
        {
            $value = stripslashes($value);
        }
    }

    /**
     * 递归utf-8转gbk
     */
    private static function toGbkDeep(&$value)
    {
        $value = YStr_Encoding::togbk($value);
        return;
        if (is_array($value))
        {
            foreach ($value as $sKey => &$vVal)
            {
                self::toGbkDeep($vVal);
            }
        }
        elseif (is_string($value))
        {
            $value = iconv('UTF-8', 'GB2312', $value);
        }
    }

}

class YStr_Check
{
    public function checkEmail($email) 
    { 
        //检查email用户和域名字符串长度，rfc规定最大不超过320,本应用限定128个字符 
        if ( strlen($email) > 128)
        {
            return FALSE;
        }
			
        //分割email地址，分隔符: '@'
        $email_array = explode("@", $email); 
        if(count($email_array) != 2)
        {
            return FALSE;
        }
	
        //检查Eamil user部分，即'@'前面部分的字符串
        $local_array = explode(".", $email_array[0]); 
        for ($i = 0; $i < count($local_array); $i++) 
        { 
		    if(!ereg("^(([A-Za-z0-9!#$%&*+/=?^_`{|}~-][A-Za-z0-9!#$%&*+/=?^_`{|}~\.-]{0,63})|(\"[^(\\|\")]{0,62}\"))$", $local_array[$i]))
            {
                return FALSE;
            } 
        } 
        ////检查Eamil 域名部分，即'@'后面部分的字符串，域名或IP地址
        if (!ereg("^\[?[0-9\.]+\]?$", $email_array[1])) 
        { 
            $domain_array = explode(".", $email_array[1]); 
            if (count($domain_array) < 2) 
            { 
                return FALSE; //域名格式不正确
            } 
            for ($i = 0; $i < count($domain_array); $i++)
            { 
                if (!ereg("^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]+))$", $domain_array[$i]))
                { 
                    return FALSE;
                } 
            } 
        } 
        return TRUE;
    }

    public static function checkMobile($mobile)
    {
        if(strlen($mobile) == 11 && preg_match("/13\d{9}|15[1235689]\d{8}|18\d{9}/", $mobile))
        {
            return TRUE;
        }
        return FALSE;
    }

    // 把字符串中的非UTF-8字符转换成U+FFFD
    // php的json_encode只支持UTF-8数据 若有非UTF-8数据 则直接返回null
    // 因此需要保证入库的数据必须是合法的utf-8数据
    public static function sanitizeUTF8($string)
    {
        if (self::isUTF8($string))
        {
            return $string;
        }

        $result = array();

        $regex =
            "/([\x01-\x7F]".
            "|[\xC2-\xDF][\x80-\xBF]".
            "|[\xE0-\xEF][\x80-\xBF][\x80-\xBF]".
            "|[\xF0-\xF4][\x80-\xBF][\x80-\xBF][\x80-\xBF])".
            "|(.)/";

        $offset = 0;
        $matches = NULL;
        while (preg_match($regex, $string, $matches, 0, $offset))
        {
            if (!isset($matches[2])) {
                $result[] = $matches[1];
            }
            else
            {
                // U+FFFD.
                $result[] = "\xEF\xBF\xBD";
            }
            $offset += strlen($matches[0]);
        }

        return implode('', $result);
    }

    public static function isUTF8($string)
    {
        return mb_check_encoding($string, 'UTF-8');
    }

    /**
        Validate an email address.
        Provide email address (raw input)
        Returns true if the email address has the email
        address format and the domain exists.
    */
    function validEmail($email)
    {
        $isValid = TRUE;
        $atIndex = strrpos($email, "@");
        if(is_bool($atIndex) && !$atIndex)
        {
            $isValid = FALSE;
        }
        else
        {
            $domain    = substr($email, $atIndex + 1);
            $local     = substr($email, 0, $atIndex);
            $localLen  = strlen($local);
            $domainLen = strlen($domain);
            if($localLen < 1 || $localLen > 64){
                $isValid = FALSE;                                           // local part length exceeded
            } else if($domainLen < 1 || $domainLen > 255){
                $isValid = FALSE;                                           // domain part length exceeded
            } else if($local[0] == '.' || $local[$localLen - 1] == '.'){
                $isValid = FALSE;                                           // local part starts or ends with '.'
            } else if(preg_match('/\\.\\./', $local)){
                $isValid = FALSE;                                           // local part has two consecutive dots
            } else if(!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)){
                $isValid = FALSE;                                           // character not valid in domain part
            } else if(preg_match('/\\.\\./', $domain)){
                $isValid = FALSE;                                           // domain part has two consecutive dots
            } else if(!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\", "", $local))){
                // character not valid in local part unless
                // local part is quoted
                if(!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\", "", $local))){
                    $isValid = FALSE;
                }
            }
            if($isValid && !(checkdnsrr($domain, "MX") || checkdnsrr($domain, "A"))){
                //检查dns是否真是存在
                $isValid = FALSE;                                         // domain not found in DNS
            }
        }
        return $isValid;
    }
}

class YStr_Encoding
{
    var $special = array(
        array(1,1,1,0,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1),
        /* 隆垄拢  楼娄    漏陋芦卢颅庐炉    虏鲁麓碌露  赂鹿潞禄录陆戮驴 */
        array(0,1,1,1,1,1,1,0,0,0,1,0,0,1,1,1,1,0,0,1,1,1,0,1,0,0,1,0,1,1,1)
        /*   芒茫盲氓忙莽      毛    卯茂冒帽    么玫枚  酶    没  媒镁每 */
        );

    public static function iconv($from, $to, $data)
    {
        if(is_string($data))
        {
            return iconv($from, $to, $data);
        }
        else
        {
            if (is_array($data))
            {
                $newdata = array();
                foreach ($data as $k => $v)
                {
                    $newk = self::iconv($from, $to, $data);
                    $newdata[$newk] = self::iconv($from, $to, $data);
                }
                return $newdata;
            }
        }
    }
    
    public static function toutf8($data)
    {
        if(is_string($data))
        {
            $newdata = iconv("GB18030", "UTF-8//IGNORE", $data);
            
            //iconv转换失败处理
            if ($newdata === false && $data !== false)
            {
                $newdata = mb_convert_encoding($data, "UTF-8", "GBK");
            }
            return $newdata;
        }
        else
        {
            if (is_array($data))
            {
                $newdata = array();
                foreach ($data as $k => $v)
                {
                    $newk = self::toutf8($k);
                    $newdata[$newk] = self::toutf8($v);
                }
                return $newdata;
            }
            return $data;
        }
    }
    
    public static function togbk($data)
    {
        if(is_string($data))
        {
            $newdata = iconv("UTF-8", "GB18030//IGNORE", $data);
            //iconv转换失败处理
            if ($newdata === false && $data !== false)
            {
                $newdata = mb_convert_encoding($data, "GBK", "UTF-8");
            }
            return $newdata;
        }
        else
        {
            if (is_array($data))
            {
                $newdata = array();
                foreach ($data as $k => $v)
                {
                    $newk = self::togbk($k);
                    $newdata[$newk] = self::togbk($v);;
                }
                return $newdata;
            }
            return $data;
        }
    }
    public static function toutf8_new($data, $to_encoding, $from_encoding)
    {
        if (is_string($data))
        {
            $new_data = mb_convert_encoding($data, $to_encoding, $from_encoding);
            return $new_data;
        }
        else
        {
            if (is_array($data))
            {
                $new_data = array();
                foreach ($data as $k => $v)
                {
                    $new_k = self::toutf8_new($k, $to_encoding, $from_encoding);
                    $new_v = self::toutf8_new($v, $to_encoding, $from_encoding);
                    $new_data[$new_k] = $new_v;
                }
                return $new_data;
            }
            return mb_convert_encoding($data, $to_encoding, $from_encoding);
        }
    }
    function unescape($str)
    {
        preg_match_all("/(?:%u[0-9A-Fa-f]{4})|&#x[0-9A-Fa-f]{4};|&#\d+;|.+|\n/U",$str,$r);
        $ar = $r[0];
        foreach($ar as $k=>$v)
        {
            if(substr($v,0,2) == "%u")
            {
                $ar[$k] = iconv("UCS-2BE","UTF-8",pack("H4",substr($v,-4)));
            }
            elseif(substr($v,0,3) == "&#x")
            {
                $ar[$k] = iconv("UCS-2BE","UTF-8",pack("H4",substr($v,3,-1)));
            }
            elseif(substr($v,0,2) == "&#")
            {
                $ar[$k] = iconv("UCS-2BE","UTF-8",pack("n",substr($v,2,-1)));
            }
        }
        return join("",$ar);
    }
    
    function contain_special($str)
    {
        $strlen = strlen($str);
        for ($i = 0; $i < $strlen - 1; )
        {
            if (ord($str[$i])&0x80 != 0)
            {
                if ($i+1 < $strlen)
                {
                    if (ord($str[$i]) >= 0xc2 && ord($str[$i]) <= 0xc3)
                    {
                        if (ord($str[$i+1]) >= 0xa1 && ord($str[$i+1]) <= 0xbf)
                        {   
                            if ($this->special[ord($str[$i]) - 0xc2][ord($str[$i+1]) - 0xa1])
                                return TRUE;
                            else
                                $i = $i+2;
                        }
                    }
                }
            }
            $i++;
        }
        
        return FALSE;
    }
    
    function junk2gbk($str, $cut = 0)
    {   
        $tempstr = $this->unescape($str);
        if($cut)
        {
            $tempstr = substr($tempstr, 0, 0 - $cut);
        }
        
        $gbkstr = iconv("utf-8", "gb18030", $tempstr);
        if (self::iconvFailure($gbkstr, $tempstr))
        {
            $cut ++;
            if ($cut < 5 && strlen($str) > 4)
            {
                //截断末尾的1到4个字符在进行转换
                return $this->junk2gbk($str, $cut);
            }
            //判断是否包含特殊字符，包含的不进行转码
            if ($this->contain_special($tempstr) == TRUE)
            {
                return $tempstr;
            }
            $gbkstr = iconv("utf-8", "8859_1", $tempstr);
            if (self::iconvFailure($gbkstr, $tempstr))
            {
                return $tempstr;
            }
        }
        else
        {   
            //部分转码认为不是UTF-8编码
            if (strlen($gbkstr) && strstr($tempstr, $gbkstr) !== FALSE)
            {
                return $tempstr;
            }
        }
        
        return $gbkstr;
    }
    
    function iconvFailure($ret, $str)
    {
        return $ret === false || ($ret === "" && strlen($str));
    }
    
    static function getOutStr($input)
    {
        if(!INNER_CHARSET || !OUT_CHARSET || INNER_CHARSET == OUT_CHARSET)
        {
            return $input;
        }
        else
        {
            if(is_string($input))
            {
                return iconv(INNER_CHARSET, OUT_CHARSET."//IGNORE" , $input);
            }
            if(is_array($input))
            {
                foreach($input as $key => $value)
                {
                    $input[$key] = self::getOutStr($value);
                }
            }
            return $input;
        }
    }
    
    static function getInnerStr($input)
    {
        if(!INNER_CHARSET || !OUT_CHARSET || INNER_CHARSET == OUT_CHARSET)
        {
            return $input;
        }
        else
        {
            if(is_string($input))
            {
                return iconv(OUT_CHARSET, INNER_CHARSET."//IGNORE" , $input);
            }
            if(is_array($input))
            {
                foreach($input as $key => $value)
                {
                    $input[$key] = self::getInnerStr($value);
                }
            }
            return $input;
        }
    }
    
    //因为junk2gbk方法会丢英文字符或数字后面的汉字，所以改写了一个转换函数，目前用于rshare应用.
    function junk2gbkRshare($value)
    {
        $gbklen = iconv_strlen($value, 'GB18030');
        $utf8len = iconv_strlen($value, 'UTF-8');
        $oldValue = $value;
        if ($gbklen == $utf8len and $utf8len > 0) 
        { 
            //目前发现的特殊字符串,不用转码: ‘什么sdfsd’
            $value = $oldValue;
        }
        else if ($utf8len) 
            { 
                //utf8编码
                $value = iconv("UTF-8", "GB18030", $value);
            } 
            else if ($gbklen === false)
                {
                    //即不是UTF8也不是GB编码
                    $value = $this->junk2gbk($value);
                }
        //gbk无须转码
        
        if (empty($value)) 
        {
            //万一判断错误转码失败（有可能GBK串里面包含几个UTF8字符），用原值。
            $value = $oldValue;
        }
        return $value;
    }
    
    //filter Private Use Zone Characters
    static function filter_PUZ($str)
    {
        //起始字符和终止字符
        $begin = 0xE000;        //57344
        $end = 0xF8FF;          //63743
        //转化成数字格式，以方便判定
        $ncr = mb_encode_numericentity($str, array(0x0, 0xFFFFFF, 0x0, 0xFFFFFF), 'UTF-8');
        //echo $ncr."\n";
        $len = strlen($ncr);
        $start = 0;
        $index = 0;
        $total = 0;
        $left = "";
        $haspuz = false;
        //一遍字符串扫描完成所有操作
        do
        {
            $total ++;
            $index ++;
            if($ncr[$index] == ';')
            {
                $value = intval(substr($ncr, 2, $index - $start));
                $ncr = substr($ncr, $index+1);
                $total ++;
                if($value >= $begin && $value <= $end)
                {
                    $haspuz = true;
                }
                else
                {
                    $left .= "&#".$value.";";
                }
                $index = 0;
            }
        }
        while($total < $len);
        //存在PUZ字符
        if($haspuz)
        {
            return mb_decode_numericentity($left, array(0x0, 0xFFFFFF, 0x0, 0xFFFFFF), 'UTF-8');
        }
        else
        {
            //如果根本无PUZ字符，原样返回
            return  $str;
        }
    }
    //不丢失字符转gbk，不能转的字符用&#174;这种unicode形式表示。
    //输出到页面时要防止&#174;这种字符做htmlspecialchars，需要的话先转utf8然后unescape再htmlspecialchars
    function tryGetGBK($name)
    {
        $tostr = "";
        for ($i = 0; $i < strlen($name); $i++)
        {
            $curbin = ord(substr($name,$i,1));
            if ($curbin < 0x80) 
            {
                $tostr .= substr($name,$i,1); 
            }
            elseif ($curbin < bindec("11000000"))
            { 
                $str = substr($name,$i,1); 
                $tostr .= "&#".ord($str).";"; 
            }
            elseif ($curbin < bindec("11100000"))
            { 
                $str = substr($name,$i,2); 
                $tostr .= "&#".self::getUnicodeChar($str).";"; 
                $i += 1; 
            }
            elseif ($curbin < bindec("11110000"))
            { 
                $str = substr($name,$i,3); 
                $gstr = iconv("UTF-8", "GB18030", $str); 
                if (!$gstr) 
                { 
                    $tostr .= "&#".self::getUnicodeChar($str).";"; 
                }else{ 
                    $tostr .= $gstr; 
                } 
                $i += 2; 
            }
            elseif ($curbin < bindec("11111000"))
            { 
                $str = substr($name,$i,4); 
                $tostr .= "&#".self::getUnicodeChar($str).";"; 
                $i += 3; 
            }
            elseif ($curbin < bindec("11111100"))
            { 
                $str = substr($name,$i,5); 
                $tostr .= "&#".self::getUnicodeChar($str).";"; 
                $i += 4; 
            }
            else
            {
                $str = substr($name,$i,6); 
                $tostr .= "&#".self::getUnicodeChar($str).";"; 
                $i += 5; 
            } 
        } 
        return $tostr; 
    }
    
    function getUnicodeChar($str) 
    { 
        $temp = ""; 
        for ($i = 0; $i < strlen($str); $i++) 
        { 
            $x = decbin(ord(substr($str,$i,1))); 
            if ($i == 0) 
            { 
                $s = strlen($str)+1; 
                $temp .= substr($x,$s,8-$s); 
            }
            else
            { 
                $temp .= substr($x,2,6); 
            } 
        } 
        return bindec($temp); 
    }
}

?>